/**
*  \file    segfile.c
*
*  \brief
*
*  \author  <ralf.huber@linde-mh.de>
*/

/*===============================================[ private includes  ]================================================*/
#define SEGFILE_LINKAGE

#include "segfile.h"
#include <string.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include "../list/list.h"
/*===============================================[ private defines   ]================================================*/

/*===============================================[ private datatypes ]================================================*/
struct segfile{
   char* fileName;
   struct section_list* seclist;
};

/*===============================================[ private variables ]================================================*/
#ifdef _WIN32
static const int PATHSEP = (int)'\\';
#else
static const int PATHSEP = (int)'/';
#endif

/*===============================================[ private functions ]================================================*/

/**
 * This function checks whether the filename, which is written at the beginning of a segmentation file, equals the
 * actual file name.
 * If the 'file' argument contains a path separator, the last path separator and anything before it are ignored.
 * If the 'file' argument contains a dot (.) after the final path separator, the dot and anything after it are ignored.
 *
 * \param   file        Path of the file.
 * \return  RC_SUCCESS  If the file name in the segmentation file is correct.\n
 *          RC_ERROR    If not.
 *
 */
RC segfile_check_filename(const char* const file) {

   /* try to open segmentation file */
   FILE* fp = fopen(file, "r");
   if (NULL == fp) {
      printf("\tError: Could not open segmentation file \"%s\".\n", file);
      return RC_ERROR;
   }

   const char* firstChar = strrchr(file, PATHSEP);
   const char* lastChar =  &(file[strlen(file)]);

   if (firstChar == NULL) {
      firstChar = file;       /* 'file' contains no path separator, use the full 'file' variable */
   } else {
      firstChar++;            /* go to first character after the last path separator */
   }

   /* compare characters from 'firstChar' to 'lastChar' with the beginning of the file content */
   char bufChar = fgetc(fp);
   while (bufChar == *firstChar && firstChar < lastChar) {
      bufChar = fgetc(fp);
      firstChar++;
   }

   fclose(fp);

   const char* lastDot = strrchr(file, '.');
   if (NULL == lastDot) {
      lastDot = lastChar;
   }

   /* after the matching sequence, bufChar must be ';' and firstChar must be '.' or '\0' */
   if (';' != bufChar || (lastDot != firstChar && lastChar != firstChar)) {
      printf("\tError: Name of segmentation file does not match the name in the segmentation file.\n");
      return RC_ERROR;
   }

   return RC_SUCCESS;
}

/**
 * This function returns the amount of segments in the segment file. It does not search whether all of them are actually
 * defined in the segmentation file - it simply reads the number between the first and second semicolon.
 *
 * \param   file  Path of the file.
 * \return  The segment count\n
 *          0 if the segment count could not be read or is higher than 30.
 */
U8 segfile_get_segment_count(const char* const file) {

   /* try to open segmentation file */
   FILE* fp = fopen(file, "r");
   if (NULL == fp) {
      printf("\tError: Could not open segmentation file \"%s\".\n", file);
      return RC_ERROR;
   }

   unsigned int segmentCount = 0;

   /* skip filename and read segment count from file */
   if (1 != fscanf(fp, "%*[^;];%2u;", &segmentCount) || segmentCount > 30) {
      printf("\tError: Segment count in \"%s\" has incorrect format.\n", file);
      segmentCount = 0;
   }

   fclose(fp);
   return (U8) segmentCount;
}

/**
 * This function reads all segment start addresses and segment sizes from the segmentation file. It reads all data that
 * it can find and it doesn't use the segment count from the file (2nd value in the segmentation file). For each segment
 * that it finds in the file, it creates a section in the 'seclist' parameter and fills it with the specified fill
 * byte. If the function encounters any error (e.g. wrongly formatted addresses, start address without corresponding
 * segment size, etc), it stops reading any further and returns RC_ERROR.
 *
 * \param   file     Path of the segmentation file.
 * \param   seclist  Section list structure to which the segments will be added.
 * \return  RC_SUCCESS  If all found segments could be added to the 'seclist'.\n
 *          RC_ERROR    If any error occured. In this case, 'seclist' contains only the sections found so far.
 *
 */
RC segfile_read_segments(const char* const file, struct section_list* seclist) {

   if (NULL == seclist) {
      printf("\tError: Parameter 'seclist' is invalid.\n");
      return RC_ERROR;
   }

   /* try to open segmentation file */
   FILE* fp = fopen(file, "r");
   if (NULL == fp) {
      printf("\tError: Could not open segmentation file \"%s\".\n", file);
      return RC_ERROR;
   }

   /* skip filename and segment count in file */
   if (0 != fscanf(fp, "%*[^;];%*2u;")) {
      printf("\tError: \"%s\" has incorrect format.\n", file);
      fclose(fp);
      return RC_ERROR;
   }

   U32 segStart = 0;
   U32 segSize = 0;
   int charCount = -1;
   int charCountA = 0;
   int charCountB = 0;

   int tmp;
   struct section* tempSection;

   while (1) {

      /* try to read two hex numbers from the segment file (address and segment size) */
      tmp = fscanf(fp, "%10x%n;%10x%n;", (int*)&segStart, &charCountA, (int*)&segSize, &charCountB);

      if (1 == tmp) {
         printf("\tError: Segment start 0x%*X has been found but no segment size.\n", charCountA-2, (unsigned int) segStart);
         fclose(fp);
         return RC_ERROR;
      } else if (0 == tmp ) {
         break;                  /* no more segments were found */
      }

      /* character count for first address is used as a template for other addresses */
      if (-1 == charCount) {
         charCount = charCountA;
      }

      /* check if the addresses of the current segment have the same format as those of previous segments */
      if (charCount != charCountA || charCount != charCountB - charCountA - 1) {
         printf("\tError: Address format not consistent among all segment start addresses and sizes.\n");
         fclose(fp);
         return RC_ERROR;
      }

      /* create section and add it to the list */
      tempSection = section_create(segSize);
      if (NULL == tempSection) {
         printf("\tError: Could not create section object.\n");
         fclose(fp);
         return RC_ERROR;
      } else {
         LIST_DATA(tempSection)->address = segStart;
         section_insert(seclist, tempSection);
      }
   }

   fclose(fp);

   /* check if some addresses occur in multiple segments */
   struct section* checkSection;
   U32 tempSection_start;
   U32 tempSection_end;
   U32 checkSection_start;
   U32 checkSection_end;

   LIST_FOR_EACH(seclist, tempSection) {
      tempSection_start = LIST_DATA(tempSection)->address;
      tempSection_end = LIST_DATA(tempSection)->address + LIST_DATA(tempSection)->size_max - 1;

      LIST_FOR_EACH(seclist, checkSection) {
         checkSection_start = LIST_DATA(checkSection)->address;
         checkSection_end = LIST_DATA(checkSection)->address + LIST_DATA(checkSection)->size_max - 1;

         if (tempSection != checkSection) {
            /* check whether "checkSection"s start address is within the "tempSection" */
            if (checkSection_start >= tempSection_start && checkSection_start <= tempSection_end) {
               printf("\tError: Sections overlap.\n");
               return RC_ERROR;
            }

            /* check whether "checkSection"s end address is within the "tempSection" */
            if (checkSection_end >= tempSection_start && checkSection_end <= tempSection_end) {
               printf("\tError: Sections overlap.\n");
               return RC_ERROR;
            }
         }
      }
   }

   return RC_SUCCESS;
}

segfile_t* segfile_open(const char* const fileName) {

   if (RC_ERROR == segfile_check_filename(fileName)) {
      return NULL;
   }

   struct section_list* seclist = section_list_create(NULL);
   if (RC_ERROR == segfile_read_segments(fileName, seclist)) {
      return NULL;
   }

   struct section* sec;
   unsigned int count;
   LIST_COUNT(seclist, &sec, &count);

   if (count != segfile_get_segment_count(fileName)) {
      printf("\tError: Specified segment count does not match the actual number of segments.\n");
      return NULL;
   }

   /* If we arrive here, the seg. file seems to be valid and it could be read. Now create object to return to caller. */

   segfile_t* segFileObj = malloc(sizeof(segfile_t));
   if (NULL == segFileObj) {
      printf("\tError: Could not allocate memory for segfile object.\n");
      return NULL;
   } else {

      segFileObj->seclist = seclist;

      segFileObj->fileName = malloc(strlen(fileName) + 1);
      if (NULL == segFileObj->fileName) {
         printf("\tError: Could not allocate memory to store the name of the segmentation file.\n");
         free(segFileObj);
         return NULL ;
      } else {
         strcpy(segFileObj->fileName, fileName);
      }
   }

   return segFileObj;
}

struct section_list* segfile_get_sections(const segfile_t* const segfile) {
   if (NULL == segfile) {
      return NULL;
   } else {
      return segfile->seclist;
   }
}

void segfile_close(segfile_t* segfile) {
   if (NULL != segfile) {
      /* free contents of segfile */
      free(segfile->fileName);
      section_list_delete(segfile->seclist);

      /* free segfile itself */
      free(segfile);
   }
}


/***********************************************************************************************************************
*                             Copyright 2013 Linde Material Handling. All rights reserved.                             *
***********************************************************************************************************************/
